package com.sailor.excel.httpclient;

import com.alibaba.ttl.threadpool.TtlExecutors;
import com.fasterxml.jackson.core.type.TypeReference;
import com.sailor.excel.httpclient.annotation.AutoCache;
import com.sailor.excel.httpclient.annotation.AutoGet;
import com.sailor.excel.httpclient.annotation.AutoPost;
import com.sailor.excel.httpclient.annotation.CookieParameter;
import com.sailor.excel.httpclient.annotation.HeaderParameter;
import com.sailor.excel.httpclient.annotation.PostBody;
import com.sailor.excel.httpclient.monitor.TaskMetricContext;
import com.sailor.excel.util.HttpClientUtil;
import com.sailor.excel.util.JsonUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.SocketTimeoutException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;


/**
 * @description: 动态代理，需要注意的是，这里用到的是JDK自带的动态代理，代理对象只能是接口，不能是类
 * @author: wwz
 * @date: 2025-05-09 18:09
 */
@Slf4j
public class AutoHttpClientProxy<T> implements InvocationHandler {


    private AutoHttpClientEnv env;

    private String applicationName;

    private static ExecutorService proxyThreadPoolExecutor = TtlExecutors.getTtlExecutorService(new ThreadPoolExecutor(
            50,
            3000,
            60, TimeUnit.SECONDS,
            new SynchronousQueue<Runnable>()));


    public AutoHttpClientProxy(String applicationName, AutoHttpClientEnv env) {
        this.applicationName = applicationName;
        this.env = env;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Object.class.equals(method.getDeclaringClass())) {
            return CompletableFuture.completedFuture(method.invoke(this, args));
        }
        return getData(method, args);
    }

    private CompletableFuture<Object> getData(Method method, Object[] args) {
        AutoCache cacheAnnotation = method.getAnnotation(AutoCache.class);
//        String key = cacheAnnotation == null ? "" : RedisKeyUtil.getKey(method, args, cacheAnnotation);
        // 获取方法返回类型中 T 类型
        Type returnType = ((ParameterizedType) method.getAnnotatedReturnType().getType()).getActualTypeArguments()[0];
        TypeReference<Object> typeReference = new TypeReference<Object>() {
            @Override
            public Type getType() {
                return returnType;
            }
        };

        String url;
        String charset = "";
        int timeout = 500;

        Map<String, String> headerparam = getHeaders(method, args);
        CompletableFuture<Object> result;

        AutoGet autoGet = method.getAnnotation(AutoGet.class);
        if (autoGet != null) {
            switch (this.env) {
                case ONLINE:
                    url = autoGet.online();
                    break;
                case BETA:
                    url = autoGet.beta();
                    break;
                default:
                    url = autoGet.dev();
                    break;
            }

            if (StringUtils.hasLength(autoGet.authorization())) {
                headerparam.put("Authorization", autoGet.authorization());
            }
            if (StringUtils.hasLength(autoGet.userAgentHeader())) {
                headerparam.put("User-Agent", autoGet.userAgentHeader());
            }
            charset = autoGet.charset();
            timeout = autoGet.timeout();
            url = getUrl(url, method, args);
            // 发起异步 GET 请求
            result = HttpClientUtil.get(url, typeReference, headerparam, timeout, charset);
        } else {
            AutoPost autoPost = method.getAnnotation(AutoPost.class);
            if (autoPost != null) {
                switch (this.env) {
                    case ONLINE:
                        url = autoPost.online();
                        break;
                    case BETA:
                        url = autoPost.beta();
                        break;
                    default:
                        url = autoPost.dev();
                        break;
                }

                if (StringUtils.hasLength(autoPost.authorization())) {
                    headerparam.put("Authorization", autoPost.authorization());
                }
                if (StringUtils.hasLength(autoPost.userAgentHeader())) {
                    headerparam.put("User-Agent", autoPost.userAgentHeader());
                }
                charset = autoPost.charset();
                timeout = autoPost.timeout();
                url = getUrl(url, method, args);
                result = HttpClientUtil.post(url, autoPost.type(), getPostBody(method, args), typeReference, headerparam, timeout, charset).toCompletableFuture();
            } else {
                throw new RuntimeException("必须设置AutoGet或AutoPost注解");
            }
        }

        String finalUrl = url;
        return tryMarkMetric(result).thenApply(urlResult -> {
//            if (cacheAnnotation != null) {
//                redisTemplate.opsForValue().set(key, JsonUtil.toString(urlResult), cacheAnnotation.liveTime(), TimeUnit.SECONDS);
//            }
            return urlResult;
        }).exceptionally(e -> {
            Throwable cause = e.getCause() != null ? e.getCause() : e;
            if (cause instanceof SocketTimeoutException) {
                log.warn("调用原接口超时: " + finalUrl, cause);
            } else {
                log.error("调用原接口报错: " + finalUrl, cause);
            }
            return null;
        });
    }

    public CompletableFuture<Object> tryMarkMetric(CompletableFuture<Object> result) {
        try {
            TaskMetricContext.Metrics metrics = TaskMetricContext.get();
            // 只有当 当前应用是 "universal-product-job" 有上下文监控信息 metrics result 非空
            if ("universal-product-job".equals(applicationName) && null != result && ObjectUtils.isNotEmpty(metrics)) {
                return result.thenApplyAsync(urlResult -> {
                    if (ObjectUtils.isNotEmpty(metrics)) {
                        TaskMetricContext.set(metrics);
                    }
                    return urlResult;
                }, proxyThreadPoolExecutor);
            }
        } catch (Exception e) {
            log.error("tryMarkMetric err", e);
        }
        return result;
    }


    private String getUrl(String baseUrl, Method method, Object[] args) {
        Parameter[] params = method.getParameters();
        if (params == null || params.length == 0)
            return baseUrl;
        for (int i = 0; i < method.getParameters().length; i++) {
            Parameter parameter = params[i];
            if (parameter.getAnnotation(CookieParameter.class) != null || parameter.getAnnotation(HeaderParameter.class) != null)
                continue;

            baseUrl = baseUrl.replace(String.format("${%s}", parameter.getName()), args[i].toString());
        }
        return baseUrl;
    }

    private Map<String, String> getHeaders(Method method, Object[] args) {
        Map<String, String> headerparam = new LinkedHashMap<>();
        Parameter[] params = method.getParameters();
        if (params == null || params.length == 0)
            return headerparam;
        for (int i = 0; i < method.getParameters().length; i++) {
            Parameter parameter = params[i];
            if (parameter.getAnnotation(HeaderParameter.class) == null) {
                continue;
            }
            headerparam.put(parameter.getName(), args[i].toString());
        }
        return headerparam;
    }

    Object getPostBody(Method method, Object[] args) {
        Parameter[] params = method.getParameters();
        if (params == null || params.length == 0)
            return null;
        for (int i = 0; i < method.getParameters().length; i++) {
            Parameter parameter = params[i];
            if (parameter.getAnnotation(PostBody.class) == null) {
                continue;
            }
            return args[i];
        }
        return null;
    }


}
